﻿using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using Un4seen.Bass;
using Un4seen.Bass.AddOn.Mix;
using Un4seen.Bass.AddOn.Tags;
//using Un4seen.Bas
using System.Threading;
using mxplay.Properties;

namespace mxplay
{
    class PlayerConfig
    {
        public float SilenceLevel
        {
            get { return Settings.Default.PlayerSilenceLevel; }
            set { Settings.Default.PlayerSilenceLevel = value; }
        }

        public int FadeTime
        {
            get { return Settings.Default.PlayerFadeTime; }
            set { Settings.Default.PlayerFadeTime = value; }
        }

        public bool NormLevel
        {
            get { return Settings.Default.PlayerNormLevel; }
            set { Settings.Default.PlayerNormLevel = value; }
        }

        public int TransFadeTime
        {
            get { return Settings.Default.PlayerTransFadeTime; }
            set { Settings.Default.PlayerTransFadeTime = value; }
        }
    }

    class PlaylistEntryEventArgs : EventArgs
    {
        public string Entry { get; set; }
        //public bool EndOfList { get; set; }

        public PlaylistEntryEventArgs(string entry)
        {
            Entry = entry;
            //EndOfList = false;
        }

        public PlaylistEntryEventArgs()
        {
            Entry = string.Empty;
            //EndOfList = false;
        }


    }

    class BassPlayer
    {
        private int active_channel_handle=0;



        public void Play()
        {
            if(active_channel_handle==0)
            {
                //активного канала нет
                //создаём
                active_channel_handle = open_another_channel(true);
                if (!BassMix.BASS_Mixer_ChannelPlay(active_channel_handle)) BassException.ThrowOnError();
                on_stream_changed();
                on_state_changed();
            }

            //не удалось открыть channel stream
            if (active_channel_handle == 0) return;

            //активный channel есть
            //смотрим на статус
            BASSActive state_mixer=Bass.BASS_ChannelIsActive(Program.BassCore.MixerHandle);
            BASSActive state_channel=BassMix.BASS_Mixer_ChannelIsActive(active_channel_handle);
            switch (state_mixer)
            {
                case BASSActive.BASS_ACTIVE_PAUSED:
                case BASSActive.BASS_ACTIVE_STOPPED:
                    if (!Bass.BASS_ChannelPlay(Program.BassCore.MixerHandle, false)) BassException.ThrowOnError();
                    on_state_changed();
                    break;
            }
            switch (state_channel)
            {
                case BASSActive.BASS_ACTIVE_PAUSED:
                case BASSActive.BASS_ACTIVE_STOPPED:
                    if (!BassMix.BASS_Mixer_ChannelPlay(active_channel_handle)) BassException.ThrowOnError();
                    on_state_changed();
                    break;
            }
        }

        public void Stop()
        {
            if (active_channel_handle != 0)
            {
                //ставим паузу
                //if (!BassMix.BASS_Mixer_ChannelPause(active_channel_handle))
                //{
                //    BassException.ThrowOnError();
                //}

                

                Bass.BASS_ChannelStop(Program.BassCore.MixerHandle);

                //позицию - на начало
                BassMix.BASS_Mixer_ChannelSetPosition(active_channel_handle, 0);
              
                //а если не удалось - то и хуй с ним, значит нельзя вернуться к началу

                on_state_changed();
            }
        }

        public void Pause()
        {
            if (active_channel_handle != 0)
            {
                BASSActive state=Bass.BASS_ChannelIsActive(Program.BassCore.MixerHandle);
                if (state == BASSActive.BASS_ACTIVE_PLAYING)
                {
                    if (!Bass.BASS_ChannelPause(Program.BassCore.MixerHandle)) BassException.ThrowOnError();
                    on_state_changed();
                }
            }
        }

        public void NextEntry()
        {
            //сохраняем текущий state
            BASSActive state=BASSActive.BASS_ACTIVE_PAUSED;
            if (active_channel_handle != 0) state = BassMix.BASS_Mixer_ChannelIsActive(active_channel_handle);

            //открываем следующий channel
            int next_chan_handle=open_another_channel(true);

            if (next_chan_handle != 0)
            {
                //и запускаем
                if (!BassMix.BASS_Mixer_ChannelPlay(next_chan_handle)) BassException.ThrowOnError();

                //следующий channel готов, старый надо остановить и освободить
                if (active_channel_handle != 0)
                {
                    BassMix.BASS_Mixer_ChannelRemove(active_channel_handle);
                    Bass.BASS_StreamFree(active_channel_handle);
                }

                //а state у миксера и так должен сохраняться

                active_channel_handle = next_chan_handle;
                //if (state == BASSActive.BASS_ACTIVE_PLAYING)
                //{
                //    if (!BassMix.BASS_Mixer_ChannelPlay(next_chan_handle)) BassException.ThrowOnError();
                //}

                on_stream_changed();
            }
        }

        public void PrevEntry()
        {
            //сохраняем текущий state
            BASSActive state=BASSActive.BASS_ACTIVE_PAUSED;
            if (active_channel_handle != 0) state = BassMix.BASS_Mixer_ChannelIsActive(active_channel_handle);

            //открываем предыдущий channel
            int new_chan_handle=open_another_channel(false);

            if (new_chan_handle != 0)
            {
                //и запускаем
                if (!BassMix.BASS_Mixer_ChannelPlay(new_chan_handle)) BassException.ThrowOnError();

                //следующий channel готов, старый надо остановить и освободить
                BassMix.BASS_Mixer_ChannelRemove(active_channel_handle);
                Bass.BASS_StreamFree(active_channel_handle);

                active_channel_handle = new_chan_handle;
                //if (state == BASSActive.BASS_ACTIVE_PLAYING)
                //{
                //    if (!BassMix.BASS_Mixer_ChannelPlay(new_chan_handle)) BassException.ThrowOnError();
                //}

                on_stream_changed();
            }
        }

        public TimeSpan Position
        {
            get
            {
                if (active_channel_handle != 0)
                {
                    return TimeSpan.FromSeconds
                        (Bass.BASS_ChannelBytes2Seconds(active_channel_handle, BassMix.BASS_Mixer_ChannelGetPosition(active_channel_handle)));
                }
                else
                {
                    return TimeSpan.Zero;
                }
            }
            set
            {
                if (active_channel_handle != 0)
                {
                    if (!BassMix.BASS_Mixer_ChannelSetPosition(active_channel_handle, Bass.BASS_ChannelSeconds2Bytes(active_channel_handle, value.TotalSeconds)))
                        BassException.ThrowOnError();
                }
            }
        }

        public TimeSpan Length
        {
            get
            {
                if (active_channel_handle != 0)
                {
                    return TimeSpan.FromSeconds(Bass.BASS_ChannelBytes2Seconds(active_channel_handle, Bass.BASS_ChannelGetLength(active_channel_handle)));
                }
                else
                {
                    return TimeSpan.Zero;
                }
            }
        }

        public float Volume
        {
            get
            {
                float ret=0;
                if (!Bass.BASS_ChannelGetAttribute(Program.BassCore.MixerHandle, BASSAttribute.BASS_ATTRIB_VOL, ref ret)) BassException.ThrowOnError();
                return ret;
            }
            set
            {
                if (!Bass.BASS_ChannelSetAttribute(Program.BassCore.MixerHandle, BASSAttribute.BASS_ATTRIB_VOL, value)) BassException.ThrowOnError();
            }
        }

        public float Pan
        {
            get
            {
                float ret = 0;
                if (!Bass.BASS_ChannelGetAttribute(Program.BassCore.MixerHandle, BASSAttribute.BASS_ATTRIB_PAN, ref ret)) BassException.ThrowOnError();
                return ret;
            }
            set
            {
                if (!Bass.BASS_ChannelSetAttribute(Program.BassCore.MixerHandle, BASSAttribute.BASS_ATTRIB_PAN, value)) BassException.ThrowOnError();
            }
        }

        public float[] Level
        {
            get
            {
                float[] ret = new float[Program.BassCore.MixerChannels];
                if (!Bass.BASS_ChannelGetLevel(Program.BassCore.MixerHandle, ret)) BassException.ThrowOnError();
                return ret;
            }
        }

        public TAG_INFO Metadata
        {
            get 
            {
                TAG_INFO ret = new TAG_INFO();
                if (!BassTags.BASS_TAG_GetFromFile(active_channel_handle, ret)) BassException.ThrowOnError();
                return ret;
            }
        }

        //public PlayerConfig PlayerConfig { get; private set; }

        public BASSActive State 
        {
            get
            {
                return Bass.BASS_ChannelIsActive(Program.BassCore.MixerHandle);
            }
        }

        public event EventHandler  MetadataUpdated;
        public event EventHandler<PlaylistEntryEventArgs>  NextPlaylistEntryNeeded;
        public event EventHandler<PlaylistEntryEventArgs>  PrevPlaylistEntryNeeded;
        public event EventHandler<PlaylistEntryEventArgs>  EntryFailed;
        public event EventHandler StreamChanged;
        public event EventHandler StateChanged;

        private void on_state_changed()
        {
            if (StateChanged != null) StateChanged(this, new EventArgs());
        }

        private SYNCPROC sync_delegate_stream_end_mixtime;
        private void sync_proc_stream_end_mixtime(int handle, int channel, int data, IntPtr user)
        {
#if DEBUG
            Program.LogMessage("stream end mixtime sync");
#endif
            ThreadPool.QueueUserWorkItem(new WaitCallback(on_stream_end_mixtime));
        }
        private void on_stream_end_mixtime(object state)
        {
            //поток из ThreadPool
            //должно вызываться в MixTime

#if DEBUG
            Program.LogMessage("stream end mixtime callback");
#endif

            //здесь открываем и подключаем в миксер следующий
            //элемент из плейлиста
            int chan_handle=open_another_channel(true);

#if DEBUG
            Program.LogMessage(string.Format("open_another channel returns 0x{0:X}", chan_handle));
#endif

            if (chan_handle == 0) return;

            //стрим добавлен в миксер в состоянии pause
            //запускаем его
#if DEBUG
            Program.LogMessage(string.Format("Call play on 0x{0:X}", chan_handle));
#endif
            if (!BassMix.BASS_Mixer_ChannelPlay(chan_handle)) BassException.ThrowOnError();

            //и теперь он active
            //вообще-то это херовый небезопасный вызов
            active_channel_handle = chan_handle;
#if DEBUG
            Program.LogMessage(string.Format("Now active channel is 0x{0:X}", active_channel_handle));
#endif

            on_stream_changed();
        }

        private void on_stream_changed()
        {
            if (StreamChanged != null) StreamChanged(this, new EventArgs());
        }

        private string on_next_entry_needed()
        {
            PlaylistEntryEventArgs e=new PlaylistEntryEventArgs();
            if (NextPlaylistEntryNeeded != null) NextPlaylistEntryNeeded(this, e);
            return e.Entry;
        }

        private string on_prev_entry_needed()
        {
            PlaylistEntryEventArgs e=new PlaylistEntryEventArgs();
            if (PrevPlaylistEntryNeeded != null) PrevPlaylistEntryNeeded(this, e);
            return e.Entry;
        }

        private void on_entry_failed(string failed_entry)
        {
            PlaylistEntryEventArgs e=new PlaylistEntryEventArgs(failed_entry);
            if (EntryFailed != null) EntryFailed(this, e);
        }

        private SYNCPROC sync_delegate_metaupdated;
        private void sync_proc_metaupdated(int handle, int channel, int data, IntPtr user)
        {
            ThreadPool.QueueUserWorkItem(new WaitCallback(on_meta_updated));   
        }
        private void on_meta_updated(object state)
        {
            //поток из ThreadPool
            if (MetadataUpdated != null)
            {
                MetadataUpdated(this, new EventArgs());
            }
        }

        private int open_another_channel(bool next)
        {
            //выполняется в разных потоках!

#if DEBUG
            Program.LogMessage("call open_another_channel");
#endif

            int chan_handle=0;
            string entry=string.Empty;

            while (true)
            {
                try
                {
                    entry = next ? on_next_entry_needed() : on_prev_entry_needed();

#if DEBUG
                    Program.LogMessage(string.Format("next_entry returns '{0}'",entry));
#endif

                    if (string.IsNullOrEmpty(entry)) break; //конец плейлиста - возвращаем ноль
#if DEBUG
                    Program.LogMessage("call open_channel");
#endif
                    chan_handle = open_channel(entry);
#if DEBUG
                    Program.LogMessage(string.Format("returns channel 0x{0:X}", chan_handle));
#endif
                    if (chan_handle != 0)
                    {
                        break;
                    }
                    else
                    {
                        on_entry_failed(entry);
                    }
                    //если channel открыли, выходим из цикла
                    //а если нет - пытаемся взять следующий entry из плейлиста
                }
                catch (Exception ex)
                {
                    //не должно быть критично
#if DEBUG
                    Program.LogMessage(string.Format("Cannot open '{0}'. Exception: {1}", entry, ex));
#endif
                }
            }

            //добавим в миксер
            if (chan_handle != 0)
            {
#if DEBUG
                Program.LogMessage(string.Format("Add channel 0x{0:X} to mixer",chan_handle));
#endif
                BASSFlag chan_mixer_flags=BASSFlag.BASS_MIXER_FILTER | BASSFlag.BASS_STREAM_AUTOFREE | BASSFlag.BASS_MIXER_PAUSE;
                if (Program.BassCore.ConfMixerBuffer) chan_mixer_flags |= BASSFlag.BASS_MIXER_BUFFER;
                if (!BassMix.BASS_Mixer_StreamAddChannel(Program.BassCore.MixerHandle, chan_handle, chan_mixer_flags))
                {
                    //а вот это уже критично - не удалось добавить в миксер
                    BassException.ThrowOnError();
                }

                //теперь надо это добро прибиндить к коллбэкам
                //meta updated:
                if (sync_delegate_metaupdated == null) sync_delegate_metaupdated = new SYNCPROC(sync_proc_metaupdated);
                BassMix.BASS_Mixer_ChannelSetSync(chan_handle, BASSSync.BASS_SYNC_META, 0, sync_delegate_metaupdated, IntPtr.Zero);
                BassMix.BASS_Mixer_ChannelSetSync(chan_handle, BASSSync.BASS_SYNC_OGG_CHANGE, 0, sync_delegate_metaupdated, IntPtr.Zero);

                //подклюяаем наблюдение за концом
                if (sync_delegate_stream_end_mixtime == null) sync_delegate_stream_end_mixtime = new SYNCPROC(sync_proc_stream_end_mixtime);
                BassMix.BASS_Mixer_ChannelSetSync(chan_handle, BASSSync.BASS_SYNC_END | BASSSync.BASS_SYNC_MIXTIME, 0, sync_delegate_stream_end_mixtime, IntPtr.Zero);
            }

            return chan_handle;
        }

        private int open_channel(string entry)
        {
            int chan_handle=0;

            //парсим entry
            if (entry.StartsWith("http://", StringComparison.InvariantCultureIgnoreCase) ||
                entry.StartsWith("ftp://", StringComparison.InvariantCultureIgnoreCase))
            {
                //это url
                chan_handle = open_channel_net(entry, null, IntPtr.Zero);
            }
            else
            {
                //считаем файлом
                chan_handle = open_channel_file(entry);
            }

            return chan_handle;
        }

        private int open_channel_file(string entry)
        {
            int chan_handle=0;

            BASSFlag bass_flag=BASSFlag.BASS_STREAM_PRESCAN | BASSFlag.BASS_STREAM_DECODE | BASSFlag.BASS_UNICODE;
            if (Program.BassCore.ConfSampleFloat) bass_flag = bass_flag | BASSFlag.BASS_SAMPLE_FLOAT;

            chan_handle = Bass.BASS_StreamCreateFile(entry, 0, 0, bass_flag);
            if (chan_handle == 0) BassException.ThrowOnError();

            return chan_handle;
        }

        private int open_channel_net(string entry,DOWNLOADPROC download_callback,IntPtr callback_data)
        {
            int chan_handle=0;

            BASSFlag bass_flag=BASSFlag.BASS_STREAM_DECODE | BASSFlag.BASS_STREAM_STATUS;
            if (Program.BassCore.ConfDownloadReatrate) bass_flag = bass_flag | BASSFlag.BASS_STREAM_RESTRATE;
            if (Program.BassCore.ConfSampleFloat) bass_flag = bass_flag | BASSFlag.BASS_SAMPLE_FLOAT;

            chan_handle = Bass.BASS_StreamCreateURL(entry, 0, bass_flag, download_callback, callback_data);
            if (chan_handle == 0) BassException.ThrowOnError();

            return chan_handle;
        }
    }

   
}
